using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;

namespace MvvmFx.Common.ViewModels
{
    /// <summary>
    /// Default ViewModelCollection - collection of <see cref="ObservableCollection{T}"/> classes
    /// </summary>
    /// <remarks>
    /// Inherits from <see cref="ViewModelBase"/> and adds events for
    /// ItemAdded, ItemRemoved, ItemMoved, and ItemRemoving, also adds public methods
    /// for accessing and manipulating <see cref="ViewModelBase"/> items
    /// by their Key property.
    /// </remarks>
    public class ViewModelCollection : ViewModelCollection<ViewModelBase>
    {

    }

    /// <summary>
    /// The ViewModelCollection class.
    /// </summary>
    /// <remarks>
    /// Inherits from <see cref="ObservableCollection{T}"/> and adds events for
    /// ItemAdded, ItemRemoved, ItemMoved, and ItemRemoving, also adds public methods
    /// for accessing and manipulating <see cref="ViewModelBase"/> items
    /// by their Key property.
    /// </remarks>
    /// <typeparam key="T">The type&lt;T&gt; of collection restricted to the base <see cref="ViewModelBase"/> class.</typeparam>
    public partial class ViewModelCollection<T> : ObservableCollection<T> where T : ViewModelBase
    {
        #region Fields

        /// <summary>
        /// Suppress property changed notifications state field.
        /// </summary>
        private bool suppressPropertyChangedNotifications = false;

        #endregion

        #region Constructor

        /// <summary>
        /// Initializes a new instance of the <see cref="MvvmFx.Common.ViewModels.ViewModelCollection{T}"/> class.
        /// </summary>
        public ViewModelCollection()
        {

        }

        #endregion

        #region Events

        /// <summary>
        /// The ItemAdded&lt;T&gt; event.
        /// </summary>
        public event EventHandler<ItemEventArgs<T>> ItemAdded;

        /// <summary>
        /// The ItemMoved&lt;T&gt; event.
        /// </summary>
        public event EventHandler<ItemEventArgs<T>> ItemMoved;

        /// <summary>
        /// The ItemRemoved&lt;T&gt; event.
        /// </summary>
        public event EventHandler<ItemEventArgs<T>> ItemRemoved;

        /// <summary>
        /// The ItemRemoving event. Cancelling this event will cancel the remove operation.
        /// </summary>
        public event EventHandler<CancelItemEventArgs<T>> ItemRemoving;

        #endregion

        #region Item

        /// <summary>
        /// Gets or sets the element in the collection with the specified key
        /// </summary>
        /// <param key="key">The key of the item to index.</param>
        /// <returns>The indexed item&lt;T&gt;.</returns>
        public T this[string key]
        {
            get
            {
                if (!string.IsNullOrEmpty(key))
                {
                    // Key's shouldn't be case-sensitive, so convert to upper to compare
                    key = key.ToUpper();

                    foreach (T item in this)
                    {
                        if (!ReferenceEquals(item.Key, null))
                        {
                            if (item.Key.ToUpper() == key)
                            {
                                return item;
                            }
                        }
                    }
                }

                return null;
            }

            set
            {
                if (value != null)
                {
                    int index = this.IndexOf(key);
                    if (index >= 0)
                    {
                        this[index] = value;
                    }
                }
            }
        }

        #endregion

        #region AddRange

        /// <summary>
        /// Add a range of items.
        /// </summary>
        /// <param key="items">The items&lt;T&gt; to add.</param>
        public void AddRange(ViewModelCollection<T> items)
        {
            foreach (T item in items)
            {
                this.Add(item);
            }
        }

        /// <summary>
        /// Add a range of items.
        /// </summary>
        /// <param key="items">The items&lt;T&gt; to add.</param>
        public void AddRange(IEnumerable<T> items)
        {
            foreach (T item in items)
            {
                Add(item);
            }
        }

        #endregion

        #region Remove

        /// <summary>
        /// Removes the item with the specified Key from the collection.
        /// </summary>
        /// <param name="key"></param>
        public void Remove(string key)
        {
            T item = this[key];
            if (item != null)
                Remove(item);
        }

        #endregion

        #region RemoveAll

        /// <summary>
        /// Remove a range of items.
        /// </summary>
        /// <param key="items">The items&lt;T&gt; to remove.</param>
        public void RemoveAll(ViewModelCollection<T> items)
        {
            foreach (T item in items)
            {
                this.Remove(item);
            }
        }

        /// <summary>
        /// Remove a range of items.
        /// </summary>
        /// <param key="items">The items&lt;T&gt; to remove.</param>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1002:DoNotExposeGenericLists")]
        public void RemoveAll(List<T> items)
        {
            items.ForEach(item => this.Remove(item));
        }

        #endregion

        #region Index Of

        /// <summary>
        /// Gets the index of an item with the given key, returning -1 if the item is not present.
        /// </summary>
        /// <param key="key">The key of the item for which the index of is desired.</param>
        /// <returns>The index of the item with the given key, or -1 if not found.</returns>
        public int IndexOf(string key)
        {
            int index = 0;

            foreach (T t in this)
            {
                if (t.Key == key)
                {
                    return index;
                }

                index++;
            }

            return -1;
        }

        #endregion

        #region Contains

        /// <summary>
        /// If there collections contains the item.
        /// </summary>
        /// <param key="key">The key to index the item from the collection.</param>
        /// <returns>If the collection contains the items.</returns>
        public bool Contains(string key)
        {
            return this[key] != null;
        }

        #endregion

        #region Sort

        /// <summary>
        /// Perform a sort on the items in this collection.
        /// The default sort is to sort by key ascending.
        /// </summary>
        public void Sort()
        {
            var unsortedList = new List<T>(this);

            unsortedList.Sort((a, b) => a.Key.CompareTo(b.Key));

            this.SuppressChangeNotifications();
            this.Clear();
            this.AddRange(unsortedList);
            this.ResumeChangeNotifications();
        }

        public void Sort(IComparer<T> comparer)
        {
            int i, j;
            T index;
            for (i = 1; i < Count; i++)
            {
                index = this[i];     //If you can't read it, it should be index = this[x], where x is i :-)
                j = i;
                while ((j > 0) && (comparer.Compare(this[j - 1], index) == 1))
                {
                    this[j] = this[j - 1];
                    j = j - 1;
                }
                this[j] = index;
            }
        }

        #endregion

        #region Collection/Item Property changed

        /// <summary>
        /// On the collection changed. Notify of the change.
        /// </summary>
        /// <param key="e">The <see cref="NotifyCollectionChangedEventArgs"/> event fired.</param>
        protected sealed override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
        {
            base.OnCollectionChanged(e);

            switch (e.Action)
            {
                case NotifyCollectionChangedAction.Add:
                    foreach (T item in e.NewItems)
                    {
                        if (!IsSuppressingPropertyChangedNotifications)
                        {
                            this.OnItemAdded(new ItemEventArgs<T>(item, e.OldStartingIndex, e.NewStartingIndex));
                        }
                    }

                    break;

                case NotifyCollectionChangedAction.Remove:
                    foreach (T item in e.OldItems)
                    {
                        if (!IsSuppressingPropertyChangedNotifications)
                        {
                            this.OnItemRemoved(new ItemEventArgs<T>(item, e.OldStartingIndex, e.NewStartingIndex));
                        }
                    }

                    break;

                case NotifyCollectionChangedAction.Replace:

                    break;

                case NotifyCollectionChangedAction.Reset:

                    foreach (T item in this)
                    {
                        if (!IsSuppressingPropertyChangedNotifications)
                        {
                            this.OnItemRemoved(new ItemEventArgs<T>(item, e.OldStartingIndex, e.NewStartingIndex));
                        }
                    }
                    break;
            }
        }

        /// <summary>
        /// On item  added. Notify of the change.
        /// </summary>
        /// <param key="e">The <see cref="ItemEventArgs&lt;T&gt;"/> event fired.</param>
        protected virtual void OnItemAdded(ItemEventArgs<T> e)
        {
            if (this.ItemAdded != null)
            {
                this.ItemAdded(this, e);
            }
        }

        /// <summary>
        /// On item  moved. Notify of the change.
        /// </summary>
        /// <param key="e">The <see cref="ItemEventArgs&lt;T&gt;"/> event fired.</param>
        protected virtual void OnItemMoved(ItemEventArgs<T> e)
        {
            if (this.ItemMoved != null)
            {
                this.ItemMoved(this, e);
            }
        }

        /// <summary>
        /// On item removed. Notify of the change.
        /// </summary>
        /// <param key="e">The <see cref="ItemEventArgs&lt;T&gt;"/> event fired.</param>
        protected virtual void OnItemRemoved(ItemEventArgs<T> e)
        {
            if (this.ItemRemoved != null)
            {
                this.ItemRemoved(this, e);
            }
        }

        /// <summary>
        /// On item removing.
        /// </summary>
        /// <param key="e"></param>
        protected virtual void OnItemRemoving(CancelItemEventArgs<T> e)
        {
            if (this.ItemRemoving != null)
            {
                this.ItemRemoving(this, e);
            }
        }

        /// <summary>
        /// Override the RemoveItem method to raise the 
        /// cancellable ItemRemoving event.
        /// </summary>
        /// <param key="index"></param>
        protected override void RemoveItem(int index)
        {
            // raise the ItemRemoving event and check the cancel flag.
            var e = new CancelItemEventArgs<T>(Items[index]);
            OnItemRemoving(e);

            if (e.Cancel == false)
            {
                base.RemoveItem(index);
            }
        }

        /// <summary>
        /// On the property changed. Notify the listening object of the changed.
        /// </summary>
        /// <param key="propertyKey">The key of the property that changed.</param>
        protected virtual void OnPropertyChanged(string propertyKey)
        {
            if (!IsSuppressingPropertyChangedNotifications)
            {
                this.OnPropertyChanged(new PropertyChangedEventArgs(propertyKey));
            }
        }

        /// <summary>
        /// Suppress the change notifications.
        /// </summary>
        public void SuppressChangeNotifications()
        {
            suppressPropertyChangedNotifications = true;
        }

        /// <summary>
        /// Resume the change notification.
        /// </summary>
        public void ResumeChangeNotifications()
        {
            suppressPropertyChangedNotifications = false;
            OnRefresh();
        }

        /// <summary>
        /// Supress property changed notifications state field.
        /// </summary>
        protected bool IsSuppressingPropertyChangedNotifications
        {
            get { return suppressPropertyChangedNotifications; }
        }

        #endregion

        #region Refresh Bindings

        /// <summary>
        /// Refresh all the bindings.
        /// </summary>
        protected void OnRefresh()
        {
            this.OnPropertyChanged("");
        }

        #endregion

        #region ToString

        /// <summary>
        /// A string represents the current object. 
        /// </summary>
        /// <returns>A <see cref="String"/> that represents the current <see cref="Object"/>.</returns>
        public override string ToString()
        {
            return string.Format("ViewModelCollection<{0}> ({1})", typeof(T), base.ToString());
        }

        #endregion
    }
}